use chrono::Utc;
use loco_rs::prelude::*;
use serde::{Deserialize, Serialize};
use uuid::Uuid;

pub use super::_entities::failed_jobs::{self, ActiveModel, Entity, Model};

#[derive(Debug, Deserialize, Serialize)]
pub struct CreateParams {
    pub uuid: String,
    pub connection: String,
    pub queue: String,
    pub payload: String,
    pub exception: String,
}

#[derive(Debug, Validate, Deserialize)]
pub struct Validator {
    #[validate(length(min = 1, message = "UUID must not be empty."))]
    pub uuid: String,
    #[validate(length(min = 1, message = "Connection must not be empty."))]
    pub connection: String,
    #[validate(length(min = 1, message = "Queue must not be empty."))]
    pub queue: String,
}

impl Validatable for ActiveModel {
    fn validator(&self) -> Box<dyn Validate> {
        Box::new(Validator {
            uuid: self.uuid.as_ref().to_owned(),
            connection: self.connection.as_ref().to_owned(),
            queue: self.queue.as_ref().to_owned(),
        })
    }
}

#[async_trait::async_trait]
impl ActiveModelBehavior for super::_entities::failed_jobs::ActiveModel {
    async fn before_save<C>(self, _db: &C, insert: bool) -> Result<Self, DbErr>
    where
        C: ConnectionTrait,
    {
        self.validate()?;
        if insert {
            let mut this = self;
            this.id = ActiveValue::Set(Uuid::new_v4().to_string());
            this.failed_at = ActiveValue::Set(Utc::now().into());
            Ok(this)
        } else {
            Ok(self)
        }
    }
}

impl Model {
    /// finds a failed job by the provided id
    ///
    /// # Errors
    ///
    /// When could not find failed job or DB query error
    pub async fn find_by_id(db: &DatabaseConnection, id: &str) -> ModelResult<Self> {
        let failed_job = failed_jobs::Entity::find()
            .filter(
                model::query::condition()
                    .eq(failed_jobs::Column::Id, id)
                    .build(),
            )
            .one(db)
            .await?;
        failed_job.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// finds a failed job by the provided uuid
    ///
    /// # Errors
    ///
    /// When could not find failed job or DB query error
    pub async fn find_by_uuid(db: &DatabaseConnection, uuid: &str) -> ModelResult<Self> {
        let failed_job = failed_jobs::Entity::find()
            .filter(
                model::query::condition()
                    .eq(failed_jobs::Column::Uuid, uuid)
                    .build(),
            )
            .one(db)
            .await?;
        failed_job.ok_or_else(|| ModelError::EntityNotFound)
    }

    /// Asynchronously creates a failed job and saves it to the database.
    ///
    /// # Errors
    ///
    /// When could not save the failed job into the DB
    pub async fn create(db: &DatabaseConnection, params: &CreateParams) -> ModelResult<Self> {
        let failed_job = failed_jobs::ActiveModel {
            uuid: ActiveValue::set(params.uuid.to_string()),
            connection: ActiveValue::set(params.connection.to_string()),
            queue: ActiveValue::set(params.queue.to_string()),
            payload: ActiveValue::set(params.payload.to_string()),
            exception: ActiveValue::set(params.exception.to_string()),
            ..Default::default()
        }
        .insert(db)
        .await?;

        Ok(failed_job)
    }
}

impl ActiveModel {
    /// Updates the failed job's payload
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_payload(
        mut self,
        db: &DatabaseConnection,
        payload: String,
    ) -> ModelResult<Model> {
        self.payload = ActiveValue::set(payload);
        Ok(self.update(db).await?)
    }

    /// Updates the failed job's exception
    ///
    /// # Errors
    ///
    /// when has DB query error
    pub async fn update_exception(
        mut self,
        db: &DatabaseConnection,
        exception: String,
    ) -> ModelResult<Model> {
        self.exception = ActiveValue::set(exception);
        Ok(self.update(db).await?)
    }
}
